import { Tween } from "@tweenjs/tween.js";
import {
  Scene,
  PerspectiveCamera,
  WebGLRenderer,
  Mesh,
  GridHelper,
  Vector3,
  Raycaster,
  Vector2,
  Color,
  MeshPhongMaterial,
  Light,
  DirectionalLight,
  DirectionalLightHelper,
  MathUtils,
  DoubleSide,
  BufferGeometry,
  LineBasicMaterial,
  Line,
  AxesHelper,
  Group,
  PlaneGeometry,
  PointsMaterial,
  Points,
  AdditiveBlending,
  TextureLoader,
} from "three";
import { GLTFLoader, OrbitControls, Sky } from "three/examples/jsm/Addons.js";
import GUI from "three/examples/jsm/libs/lil-gui.module.min.js";
import Stats from "three/examples/jsm/libs/stats.module.js";
import { createCloudGeometry } from "./utils/cloud";

export interface TweenType extends Tween {
  name?: string;
}

// Three 基础类
class Three {
  dom: HTMLElement; // dom 元素
  scene: Scene; // 场景
  camera: PerspectiveCamera; // 相机
  renderer: WebGLRenderer; // 渲染器
  light: Light; // 灯光
  control: OrbitControls; // 轨道控制器
  gui?: GUI; // GUI
  stats?: Stats; // 性能
  sky?: Sky; // 天空
  animations: Set<TweenType> = new Set(); // 动画
  // 初始化
  constructor(dom: HTMLElement, help: boolean = false) {
    this.dom = dom;

    this.scene = this.addScene();

    if (help) {
      this.gui = this.addGui();
      this.stats = this.addStats();
      this.addAxes();
    }

    this.camera = this.addCamera();

    this.light = this.addLight();

    this.renderer = this.addRender();

    this.control = this.addControl();

    {
      // 监听窗口变化
      window.addEventListener("resize", this.onResize.bind(this));
    }

    this.animate();
  }

  // 添加场景
  addScene() {
    const scene = new Scene();
    scene.background = new Color(0x000000);
    return scene;
  }

  // 添加相机
  addCamera() {
    const beforePosition = new Vector3(800, -400, 300);
    const defaultPosition = new Vector3(25, -33, 6);
    const defaultLookAt = new Vector3(0, 0, 0);
    const defaultUp = new Vector3(0, 0, 1);
    const camera = new PerspectiveCamera(
      75,
      this.dom.clientWidth / this.dom.clientHeight,
      0.1,
      1000,
    );

    camera.position.copy(beforePosition);
    camera.lookAt(defaultLookAt);
    camera.up = defaultUp;

    const tween: TweenType = new Tween(beforePosition)
      .to(defaultPosition, 1000)
      .onStart(() => {
        camera.position.copy(beforePosition);
      })
      .onUpdate((obj) => {
        camera.position.copy(obj);
      })
      .onComplete(() => {
        this.animations.delete(tween);
        console.log(this.animations);
      })
      .start();

    this.animations.add(tween);

    this.scene.add(camera);

    if (this.gui) {
      const folder = this.gui.addFolder("相机");
      folder.close();
      folder.add(camera.position, "x", -1000, 1000).name("x").listen();
      folder.add(camera.position, "y", -1000, 1000).name("y").listen();
      folder.add(camera.position, "z", -1000, 1000).name("z").listen();
    }

    return camera;
  }

  // 添加灯光
  addLight() {
    const pointLight = new DirectionalLight(0xffffff, 1);
    const helper = new DirectionalLightHelper(pointLight, 5);
    this.scene.add(helper);
    this.scene.add(pointLight);
    pointLight.position.set(0, 200, 200);
    pointLight.target.position.set(0, 0, 0);

    if (this.gui) {
      const folder = this.gui.addFolder("灯光");
      folder.close();
      folder.add(pointLight, "visible").name("打开");
      folder.add(helper, "visible").name("lightHelper");
      folder.add(pointLight.position, "x", -1000, 1000).name("x");
      folder.add(pointLight.position, "y", -1000, 1000).name("y");
      folder.add(pointLight.position, "z", -1000, 1000).name("z");
    }

    return pointLight;
  }

  // 添加网格
  addGrid() {
    const grid = new GridHelper(1000, 1000, 0xffffff, 0x555555);
    grid.rotateX(90 * (Math.PI / 180));
    grid.rotateOnAxis(new Vector3(1, 0, 0), 90 * (Math.PI / 180));
    grid.position.y = 0;
    this.scene.add(grid);
  }

  // 添加坐标轴
  addAxes() {
    const axesHelper = new AxesHelper(100);
    this.scene.add(axesHelper);
  }

  // 绘制天空
  addSky() {
    const sky = new Sky();
    sky.scale.setScalar(45000);

    const phi = MathUtils.degToRad(90);
    const theta = MathUtils.degToRad(180);
    const sunPosition = new Vector3().setFromSphericalCoords(0.2, phi, theta);

    sky.material.uniforms.sunPosition.value = sunPosition;

    this.sky = sky;

    this.scene.add(sky);

    const folder = this.gui?.addFolder("天空");

    if (folder) {
      // folder.close();
      folder.add(sky.scale, "x").name("scale-x");
      folder.add(sky.scale, "y").name("scale-y");
      folder.add(sky.scale, "z").name("scale-z");
      folder
        .addColor(sky.material.uniforms.sunPosition, "value")
        .name("太阳位置");
    }
  }

  // 添加星空效果
  async addStarSky(starCount: number = 500, distance: number = 200) {
    const folder = this.gui?.addFolder("星空");

    // 星星
    {
      const starGroup = new Group();
      starGroup.name = "star";
      const starTexture = await new TextureLoader().load("/textures/star.png");

      for (let i = 0; i <= starCount; i++) {
        const starGeometry = new BufferGeometry();

        const position = new Vector3(random(), random(), random());
        position.normalize();
        position.multiplyScalar(distance);
        starGeometry.setFromPoints([position]);

        const starMaterial = new PointsMaterial();
        starMaterial.color = new Color(
          Math.random(),
          Math.random(),
          Math.random(),
        );
        starMaterial.transparent = true;
        starMaterial.opacity = Math.random();
        starMaterial.map = starTexture;

        starMaterial.size = Math.random() * 10;
        starMaterial.depthWrite = false;
        starMaterial.blending = AdditiveBlending;

        const starMesh = new Points(starGeometry, starMaterial);

        if (i % 10 === 0) {
          const tween = new Tween(starMesh.position)
            .to(
              {
                x: 1000 * Math.random() - 500,
                y: 1000 * Math.random() - 500,
                z: 1000 * Math.random() - 500,
              },
              3000,
            )
            .start()
            .repeat(Infinity);
          this.animations.add(tween);
        }

        starGroup.add(starMesh);
      }

      this.scene.add(starGroup);

      if (folder) {
        const starFolder = folder.addFolder("星星");
        starFolder.close();
        starFolder.add(starGroup, "visible").name("显示");
      }
    }

    // 星云
    {
      const params = {
        size: 3,
        branch: 4,
        radius: 1000, // 半径
        count: 15000, // 数量
        rotateScale: 0.66, // 弯曲度
        color: "#a93d5d", // 颜色
        endColor: "#829cce", // 结束颜色
        animationTime: 60000, // 动画时间
        centerPosition: new Vector3(-100, 500, 150), // 中心位置
        rotateX: Math.PI / 2, // 旋转X
        rotateY: Math.PI / 8, // 旋转Y
        rotateZ: Math.PI / 6, // 旋转Z
      };

      const cloudTexture = await new TextureLoader().load(
        "/textures/cloud.png",
      );

      const buffergeometry = createCloudGeometry(params);

      const cloudMaterial = new PointsMaterial({
        size: params.size, // 大小
        map: cloudTexture, // 纹理
        depthWrite: false, // 不写入深度
        vertexColors: true, // 使用顶点颜色
        blending: AdditiveBlending, // 混合模式
        sizeAttenuation: true, // 大小衰减
      });

      const cloudMesh = new Points(buffergeometry, cloudMaterial);
      cloudMesh.name = "cloud";
      cloudMesh.position.copy(params.centerPosition);
      cloudMesh.rotateX(params.rotateX);
      cloudMesh.rotateY(params.rotateY);
      cloudMesh.rotateZ(Math.PI / 2);

      const tween = new Tween({ rotate: 0 })
        .to({ rotate: -2 * Math.PI }, params.animationTime)
        .repeat(Infinity)
        .onUpdate((obj) => {
          cloudMesh.rotation.z = obj.rotate;
        })
        .start();

      this.animations.add(tween);

      this.scene.add(cloudMesh);

      if (folder) {
        const cloudFolder = folder.addFolder("星云");
        cloudFolder.close();
        cloudFolder
          .add(params, "size", 0, 10)
          .step(0.01)
          .name("size")
          .onChange((value) => {
            cloudMesh.material.size = value;
          });
        cloudFolder
          .add(params, "branch", 0, 10)
          .step(1)
          .name("branch")
          .onChange((value) => {
            params.branch = value;
            cloudMesh.geometry = createCloudGeometry(params);
          });
        cloudFolder
          .add(params, "radius", 0, 10000)
          .name("radius")
          .onChange((value) => {
            params.radius = value;
            cloudMesh.geometry = createCloudGeometry(params);
          });
        cloudFolder
          .add(params, "count", 0, 100000)
          .step(1)
          .name("count")
          .onChange((value) => {
            params.count = value;
            cloudMesh.geometry = createCloudGeometry(params);
          });
        cloudFolder
          .add(params, "rotateScale", 0, 1)
          .step(0.01)
          .name("rotateScale")
          .onChange((value) => {
            params.rotateScale = value;
            cloudMesh.geometry = createCloudGeometry(params);
          });
        cloudFolder
          .add(params, "animationTime", 0, 60000)
          .step(1000)
          .name("animationTime")
          .onChange((value) => {
            tween.stop();
            tween.duration(value);
            tween.start();
          });
        cloudFolder
          .addColor(params, "color")
          .name("color")
          .onChange((value) => {
            console.log(value);
            params.color = value;
            cloudMesh.geometry = createCloudGeometry(params);
          });

        cloudFolder
          .addColor(params, "endColor")
          .name("endColor")
          .onChange((value) => {
            console.log(value);
            params.endColor = value;
            cloudMesh.geometry = createCloudGeometry(params);
          });
      }
    }

    function random() {
      return Math.random() * 2 - 1;
    }
  }

  // 绘制网格地面
  addGridGround(indent: number = 50) {
    const folder = this.gui?.addFolder("地面");
    folder?.close();
    const group = new Group();
    group.name = "ground";
    // 绘制网格
    {
      const lineGeometry = new BufferGeometry();
      lineGeometry.setFromPoints([
        new Vector3(-1, 0, 0),
        new Vector3(0, 0, 0),
        new Vector3(1, 0, 0),
      ]);
      const lineMaterial = new LineBasicMaterial();
      lineMaterial.color = new Color(0x3353bb2);
      lineMaterial.side = DoubleSide;
      lineMaterial.transparent = true;
      lineMaterial.opacity = 0.2;

      const lineMaterial2 = new LineBasicMaterial();
      lineMaterial2.color = new Color(0x454591);
      lineMaterial2.side = DoubleSide;
      lineMaterial2.transparent = true;
      lineMaterial2.opacity = 0.5;

      const line = new Line(lineGeometry, lineMaterial);
      const lineGroup = new Group();
      lineGroup.name = "line";

      for (let i = 0; i <= 1; i++) {
        for (let j = -indent; j <= indent; j++) {
          const copyLine = line.clone();
          copyLine.scale.set(indent, 1, 1);

          if (j % 5 === 0) {
            copyLine.material = lineMaterial2;
          }

          if (i === 0) {
            copyLine.position.y = j;
          } else {
            copyLine.rotateZ(90 * (Math.PI / 180));
            copyLine.position.x = j;
          }

          lineGroup.add(copyLine);
        }
      }

      group.add(lineGroup);

      if (folder) {
        folder
          .addColor(lineMaterial, "color")
          .name("线条一颜色")
          .onChange((value) => {
            console.log(value.getHexString());
          });
        folder.add(lineMaterial, "opacity", 0, 1).name("线条一透明度");
        folder
          .addColor(lineMaterial2, "color")
          .name("线条二颜色")
          .onChange((value) => {
            console.log(value.getHexString());
          });
        folder.add(lineMaterial2, "opacity", 0, 1).name("线条二透明度");
      }
    }

    // 绘制地面
    {
      const ground = new PlaneGeometry(indent * 2, indent * 2);
      const groundMaterial = new MeshPhongMaterial();
      groundMaterial.color = new Color(0x475cdc);
      const groundMesh = new Mesh(ground, groundMaterial);
      groundMesh.position.y = -0.2;
      groundMesh.receiveShadow = true;

      group.add(groundMesh);

      if (folder) {
        folder
          .addColor(groundMaterial, "color")
          .name("地面颜色")
          .onChange((value) => {
            console.log(value.getHexString());
          });
      }
    }

    this.scene.add(group);
  }

  // 绘制圆形地面
  addCircleGround(radius: number = 100) {
    console.log(radius);
    const group = new Group();
    group.name = "ground";
    {
    }
    this.scene.add(group);
  }

  // 添加 GUI
  addGui() {
    const gui = new GUI();
    return gui;
  }

  // 添加性能
  addStats() {
    const stats = new Stats();
    this.dom.appendChild(stats.dom);
    return stats;
  }

  // 添加轨道控制器
  addControl() {
    const control = new OrbitControls(this.camera, this.renderer.domElement);
    return control;
  }

  // 添加渲染器
  addRender() {
    const renderer = new WebGLRenderer();
    renderer.setSize(this.dom.clientWidth, this.dom.clientHeight);
    this.dom.appendChild(renderer.domElement);
    return renderer;
  }

  // 监听窗口变化
  onResize() {
    this.renderer.setSize(this.dom.clientWidth, this.dom.clientHeight);
    this.camera.aspect = this.dom.clientWidth / this.dom.clientHeight;
    this.camera.updateProjectionMatrix();
  }

  // 渲染
  render() {
    this.animations.forEach((tween) => {
      tween.update();
    });
    if (this.stats) {
      this.stats.update();
    }
    this.renderer.render(this.scene, this.camera);
  }

  // 动画
  animate() {
    this.control.update();
    this.render();
    requestAnimationFrame(this.animate.bind(this));
  }

  // 添加 gltf 模型
  addGltf(path: string): Promise<Group> {
    return new Promise((resolve) => {
      const loader = new GLTFLoader();
      loader.load(path, (gltf) => {
        console.log(gltf);
        gltf.scene.traverse((child) => {
          if (child instanceof Mesh) {
            child.material.emissive = child.material.color;
          }
        });

        gltf.scene.rotateX(Math.PI / 2);
        this.scene.add(gltf.scene);
        resolve(gltf.scene);
      });
    });
  }

  // 添加事件
  addEvent() {
    this.renderer.domElement.addEventListener("click", (event) => {
      const mouseX = (event.clientX / this.dom.clientWidth) * 2 - 1;
      const mouseY = -(event.clientY / this.dom.clientHeight) * 2 + 1;
      const raycaster = new Raycaster();

      raycaster.setFromCamera(new Vector2(mouseX, mouseY), this.camera);

      const intersects = raycaster.intersectObjects(this.scene.children);

      if (intersects.length > 0) {
        console.log(intersects);
        intersects.map((intersect) => {
          const mesh = intersect.object as Mesh;
          if (mesh.name === "club1") {
            // mesh.material.wireframe = !mesh.material.wireframe;
          }
        });
      }
    });
  }
}

export default Three;
